package com.baoxin.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import javafx.concurrent.Worker;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.*;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;

/**
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *   基于内存的认证
 *      自定义了类 继承自 WebSecurityConfigurerAdapter, 进而实现对 Spring Security 更多的自定义配置。
 *      基于内存的用户配置在配置角色时不需要添加“ROLE_”前缀。
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 * @Author: Rose
 * @Date: 2020/4/14 12:06
 * @version: 1.0.0
 */
@Configuration
public class MyWebSecurityConfig  extends WebSecurityConfigurerAdapter {


    /**
     *  Spring Security 提供了多种密码加密方案，官方推荐使用 BCryptPasswordEncoder ， BCryptPasswordEncoder 使用
     *  BCrypt 强哈希函数，开发者在使用时，可以选择提供strength 和   SecureRandom 实例，strength越大， 密钥的迭代次数越多，
     *  密钥迭代次数为 2^strength. strength 取值在4-31之间，默认为10
     *
     * @return
     */
    @Bean
    public  PasswordEncoder  passwordEncoder(){
        //return NoOpPasswordEncoder.getInstance();
        return new BCryptPasswordEncoder(10);
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //super.configure(auth);
        auth.inMemoryAuthentication()
                .withUser("admin").password("123").roles("ADMIN","USER")
                .and()
                .withUser("sang").password("1234").roles("USER")
                .and()
                .withUser("root").password("123").roles("ADMIN","DBA");
    }


    /**
     *   如果业务比较复杂， 开发者可以配置多个HttpSecurity, 实现对WebSecurityConfigurerAdapter 的多次扩展
     *
     *
     *
     * @param http
     * @throws Exception
     */

    @Override
    public void configure(HttpSecurity http) throws Exception {
        // authorizeRequests() 方法开启HttpSecurity 的配置
        http.authorizeRequests()
                .antMatchers("/admin/**")
                .hasRole("ADMIN")
                .antMatchers("/user/**")
                .access("hasAnyRole('ADMIN','USER')")
                .antMatchers("/db/**")
                .access("hasRole('ADMIN') and hasRole('DBA')")
                .anyRequest()
                .authenticated()
                .and()
                .formLogin() // 表示开启表单登陆
                .loginPage("/login_page") // 配置了LoginPage 后， 如果用户未授权就访问一个需要授权才能访问的接口，就会自动跳转到 login_page 页面让用户登陆。这个login_page  就是开发者自定义的登陆页面。而不再是Spring Sercurity 提供的默认登陆页
                .loginProcessingUrl("/login") // 配置登陆请求处理接口，无论是自定义登陆页面还是移动端登陆，都需要使用该接口， 登陆参数中 用户名鼻血为 username 密码必须为password . 配置
                // loginProcessingUrl 接口主要是方便 Ajax 或者移动端调用登陆接口。
                .usernameParameter("name") // 定义认证所需的用户名和密码的参数名， 默认用户名参数时 username 密码参数时password ,可以在这里自定义
                .passwordParameter("passwd")
                .successHandler(new AuthenticationSuccessHandler() {  // 登陆成功的处理逻辑
                    /**
                     * onAuthenticationSuccess 方法的第三个参数一帮用来获取 当前登陆用户的信息，在登陆成功后，可以获取当前登陆用户的信息一起返回给客户端
                     * @param request
                     * @param response
                     * @param authentication
                     * @throws IOException
                     * @throws ServletException
                     */
                    @Override
                    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
                        Object principal = authentication.getPrincipal();
                        response.setContentType("application/json?charset=utf-8");
                        PrintWriter writer = response.getWriter();
                        response.setStatus(200);
                        Map<String,Object> map = new HashMap<>();
                        map.put("status",200);
                        map.put("message",principal);
                        ObjectMapper objectMapper = new ObjectMapper();
                        writer.write(objectMapper.writeValueAsString(map));
                        writer.flush();
                        writer.close();
                    }
                })
                .failureHandler(new AuthenticationFailureHandler() { //  登陆失败的处理逻辑
                    /**
                     *  登陆失败的回调方法，
                     * AuthenticationException 参数， 通过这个异常参数可以获取登陆失败的原因，进而个用户一个明确的提示。
                     * @param request
                     * @param response
                     * @param exception
                     * @throws IOException
                     * @throws ServletException
                     */
                    @Override
                    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
                        response.setContentType("application/json?charset=utf-8");
                        PrintWriter writer = response.getWriter();
                        response.setStatus(401);
                        Map<String,Object> map = new HashMap<>();
                        map.put("status",401);
                        if(exception instanceof LockedException){

                            map.put("message","账户被锁定，登陆失败");
                        } else if( exception instanceof BadCredentialsException){

                            map.put("message","账户名或密码输入错误，登陆失败");
                        } else  if(exception instanceof DisabledException){

                            map.put("message","账户被禁用，登陆失败");
                        } else  if( exception instanceof AccountExpiredException){

                            map.put("message","账户已过期，登陆失败");
                        } else  if ( exception instanceof CredentialsExpiredException){

                            map.put("message","密码已过期，登陆失败");
                        }else{

                            map.put("message","登陆失败");
                        }
                        ObjectMapper objectMapper = new ObjectMapper();
                        writer.write(objectMapper.writeValueAsString(map));
                        writer.flush();
                        writer.close();
                    }
                })
                .and()
                .logout() // 开启注销登陆配置
                .logoutUrl("/logout") // 配置注销登陆请求URL 为“/logout”,默认也是“/logout”
                .clearAuthentication(true) // 是否清除 身份认证信息，默认为true ,表示清除
                .invalidateHttpSession(true) // 是否使得Session 失效，默认为 true
                .addLogoutHandler(new LogoutHandler() {  // 配置一个LogoutHandler , 开发者可以在LogoutHandler 中完成一些数据清除工作
                    // 例如 清除Cookie,
                    @Override
                    public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {

                    }
                })
                // 开发者可以在这里处理 注销成功后的业务逻辑。 例如返回一段 JSON 提示或者 跳转到登陆页面等。
                .logoutSuccessHandler(new LogoutSuccessHandler() {
                    @Override
                    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
                        response.sendRedirect("/login_page");
                    }
                })
                .permitAll() // 表示和登陆相关的接口 都不需要认证 即可访问。
                .and()
                .csrf() // 关闭csrf
                .disable();

    }
}
